/*
 * GL_Triange.h
 *
 * Created 5/24/2009 By Johnny Huynh
 *
 * Version 00.00.01 5/24/2009
 *
 * Copyright Information:
 * All content copyright © 2009 Johnny Huynh. All rights reserved.
 */
 
 // GL_Triangle.h contains all the functions optimized for graphics language triangles and primitive triangle functions

 #ifndef GL_TRIANGLE_H
 #define GL_TRIANGLE_H
 
 template< typename TYPENAME > class GL_Triangle;
 
 typedef GL_Triangle<GLfloat> GL_Trianglef;
 typedef GL_Triangle<GLdouble> GL_Triangled;
 
 #include "Triangle.h"
  
 #include "Vector3.h"
 #include "OpenGL_Headers.h"
 #include "OrientationMatrix.h"
 #include <math.h>
 #include <vector>

 #define OPTIMIZE_GL_TRIANGLE
 
 // Global Functions
 template< typename TYPENAME > inline void copyTriangles( GL_Triangle<TYPENAME>* const dest, 
                                                          const GL_Triangle<TYPENAME>* const src, const size_t size );
 template< typename TYPENAME > inline void getUniqueVertices( std::vector< Vector3<TYPENAME> >& uniqueVertices, 
                                                      const GL_Triangle<TYPENAME>* const triangles_Ptr, size_t size );
 template< typename TYPENAME > inline void getUniqueVertices( std::vector< Vector3<TYPENAME> >& uniqueVertices, 
                                          const GL_Triangle<TYPENAME>* const * const triangle_Ptrs_Ptr, size_t size );

 template< typename TYPENAME >
 class GL_Triangle : public Triangle<TYPENAME>
 {
 // Data Members
 protected:
    TYPENAME area;
    Vector3<TYPENAME> center, normal;  // the center and the normal unit vector of the triangle's face
                                       // the normal unit vector assumes counter-clockwise winding
 
 // Local Functions
 public:
    GL_Triangle( const TYPENAME& ax, const TYPENAME& ay, const TYPENAME& az,
              const TYPENAME& bx, const TYPENAME& by, const TYPENAME& bz,
              const TYPENAME& cx, const TYPENAME& cy, const TYPENAME& cz );
    GL_Triangle( const Vector3<TYPENAME>& a, const Vector3<TYPENAME>& b, const Vector3<TYPENAME>& c );
    GL_Triangle( const GL_Triangle<TYPENAME>& t );
    GL_Triangle( const Triangle<TYPENAME>& t );
    ~GL_Triangle();
    inline GL_Triangle<TYPENAME>& operator=( const GL_Triangle<TYPENAME>& t );
    inline GL_Triangle<TYPENAME>& operator=( const Triangle<TYPENAME>& t );
#ifdef OPTIMIZE_GL_TRIANGLE
    virtual inline const TYPENAME getArea() const;
    virtual inline const Vector3<TYPENAME> getCenter() const;
    virtual inline const Vector3<TYPENAME> getNormal() const;
    
 // Friend Functions
	template <typename TYPENAME> friend inline GL_Triangle<TYPENAME> operator*( GL_Triangle<TYPENAME> t, const TYPENAME& scale );
	template <typename TYPENAME> friend inline GL_Triangle<TYPENAME> operator*( const TYPENAME& scale, GL_Triangle<TYPENAME> t );
    template <typename TYPENAME> friend inline const TYPENAME& area( const GL_Triangle<TYPENAME>& t );
    template <typename TYPENAME> friend inline const Vector3<TYPENAME>& center( const GL_Triangle<TYPENAME>& t );
    template <typename TYPENAME> friend inline const Vector3<TYPENAME>& normal( const GL_Triangle<TYPENAME>& t );
    template <typename TYPENAME> friend inline Vector3<TYPENAME> center( std::vector< const GL_Triangle<TYPENAME>* >& triangles );
    template <typename TYPENAME> friend inline Vector3<TYPENAME> center( const std::vector< const GL_Triangle<TYPENAME> >& triangles );
#endif // OPTIMIZE_GL_TRIANGLE
    template <typename TYPENAME> friend inline GL_Triangle<TYPENAME>& apply( GL_Triangle<TYPENAME>& t, 
                                                                            const OrientationMatrix3<TYPENAME>& orientation );
    template <typename TYPENAME> friend inline GL_Triangle<TYPENAME>& apply( GL_Triangle<TYPENAME>& t, 
                                                                            const Vector3<TYPENAME>& translation );
    template <typename TYPENAME> friend inline GL_Triangle<TYPENAME>& apply( GL_Triangle<TYPENAME>& t, 
                                                                            const OrientationMatrix3<TYPENAME>& orientation,                                                                              const Vector3<TYPENAME>& translation );
    template <typename TYPENAME> friend inline void print( const GL_Triangle<TYPENAME>& t );
 };
 
 /** GLOBAL FUNCTIONS **/
 
 /**
  * copyTriangles() makes a copy the of the triangles located at the src address to the dest address.
  * The number of triangles copied is equal to the specified size.
  *
  * @param (GL_Triangle<TYPENAME>* const) dest
  * @param (const GL_Triangle<TYPENAME>* const) src
  * @param (const size_t) size
  */
 template< typename TYPENAME >
 inline void copyTriangles( GL_Triangle<TYPENAME>* const dest, const GL_Triangle<TYPENAME>* const src, const size_t size )
 {
    memcpy( dest, src, size * sizeof( GL_Triangle<TYPENAME> ) );
 }
 
 /**
  * getUniqueVertices() finds the unique vertices in the set of triangles specified and inserts
  * the vertices into the specified uniqueVertices list.
  *
  * @param (std::vector< Vector3<TYPENAME> >&) uniqueVertices - a std::vector (supposely should be empty)
  * @param (const GL_Triangle<TYPENAME>* const) triangles_Ptr - pointer to an array of triangles
  * @param (size_t) size - the number of triangles
  */
 template <typename TYPENAME>
 inline void getUniqueVertices( std::vector< Vector3<TYPENAME> >& uniqueVertices, 
                                                      const GL_Triangle<TYPENAME>* const triangles_Ptr, size_t size )
 {
    // for each triangle
    for ( size_t i( 0 ); i < size; ++i )
    {
        const Vector3<TYPENAME>& a =  triangles_Ptr[i].getEndPointA();
        const Vector3<TYPENAME>& b =  triangles_Ptr[i].getEndPointB();
        const Vector3<TYPENAME>& c =  triangles_Ptr[i].getEndPointC();
        
        if ( a != b && a != c )
        {
            uniqueVertices.push_back( a );
            if ( b != c )
            {
                uniqueVertices.push_back( b );
                uniqueVertices.push_back( c );
            }
            else // b == c, so we only include one of the vertices
            {
                uniqueVertices.push_back( b );
            }
        }
        else // a == b || a == c
        {
            if ( b != c )
            {
                uniqueVertices.push_back( b );
                uniqueVertices.push_back( c );
            }
            else // b == c, so we only include one of the vertices
            {
                uniqueVertices.push_back( b );
            }
        }
    }
    
    // uniqueVertices should now contain vertices from the triangles
    // sort uniqueVertices by the y-axis, if equal, then x-axis, if equal, then z-axis
    introsort< Vector3<TYPENAME> >( &uniqueVertices[0], uniqueVertices.size(), compareByYXZ );
    
    size_t numberOfVerticesToRemoveIndex( uniqueVertices.size() ); // keep track of the number of vertices to remove
    size_t vertex_index( uniqueVertices.size() );
    if ( vertex_index > 1 )
        do
        {
            --vertex_index;
            
            if ( uniqueVertices[ vertex_index ] == uniqueVertices[ vertex_index - 1 ] )
            {
                // swap to beginning of list for comprehensive removal later
                std::swap( uniqueVertices[ vertex_index ], uniqueVertices[ --numberOfVerticesToRemoveIndex ] );
            }
            
        } while ( vertex_index > 1 );
    
    // Remove the duplicate vertexes
    uniqueVertices.erase( uniqueVertices.begin() + numberOfVerticesToRemoveIndex, uniqueVertices.end() );
 }
 
 /**
  * getUniqueVertices() finds the unique vertices in the set of triangles specified and inserts
  * the vertices into the specified uniqueVertices list.
  *
  * @param (std::vector< Vector3<TYPENAME> >&) uniqueVertices - a std::vector (supposely should be empty)
  * @param (const GL_Triangle<TYPENAME>* const *) triangle_Ptrs_Ptr - pointer to an array of triangle pointers
  * @param (size_t) size - the number of triangles
  */
 template <typename TYPENAME>
 inline void getUniqueVertices( std::vector< Vector3<TYPENAME> >& uniqueVertices, 
                                            const GL_Triangle<TYPENAME>* const * const triangle_Ptrs_Ptr, size_t size )
 {
    // for each triangle
    for ( size_t i( 0 ); i < size; ++i )
    {
        const Vector3<TYPENAME>& a =  triangle_Ptrs_Ptr[i]->getEndPointA();
        const Vector3<TYPENAME>& b =  triangle_Ptrs_Ptr[i]->getEndPointB();
        const Vector3<TYPENAME>& c =  triangle_Ptrs_Ptr[i]->getEndPointC();
        
        if ( a != b && a != c )
        {
            uniqueVertices.push_back( a );
            if ( b != c )
            {
                uniqueVertices.push_back( b );
                uniqueVertices.push_back( c );
            }
            else // b == c, so we only include one of the vertices
            {
                uniqueVertices.push_back( b );
            }
        }
        else // a == b || a == c
        {
            if ( b != c )
            {
                uniqueVertices.push_back( b );
                uniqueVertices.push_back( c );
            }
            else // b == c, so we only include one of the vertices
            {
                uniqueVertices.push_back( b );
            }
        }
    }
    
    // uniqueVertices should now contain vertices from the triangles
    // sort uniqueVertices by the y-axis, if equal, then x-axis, if equal, then z-axis
    introsort< Vector3<TYPENAME> >( &uniqueVertices[0], uniqueVertices.size(), compareByYXZ );
    
    size_t numberOfVerticesToRemoveIndex( uniqueVertices.size() ); // keep track of the number of vertices to remove
    size_t vertex_index( uniqueVertices.size() );
    if ( vertex_index > 1 )
        do
        {
            --vertex_index;
            
            if ( uniqueVertices[ vertex_index ] == uniqueVertices[ vertex_index - 1 ] )
            {
                // swap to beginning of list for comprehensive removal later
                std::swap( uniqueVertices[ vertex_index ], uniqueVertices[ --numberOfVerticesToRemoveIndex ] );
            }
            
        } while ( vertex_index > 1 );
    
    // Remove the duplicate vertexes
    uniqueVertices.erase( uniqueVertices.begin() + numberOfVerticesToRemoveIndex, uniqueVertices.end() );
 }
 
 /** LOCAL FUNCTIONS **/
 
 /**
  * Constructor
  * The constructor takes in the three positions that define the triangle.
  *
  * @param (const TYPENAME&) ax, (const TYPENAME&) ay, (const TYPENAME&) az
  * @param (const TYPENAME&) bx, (const TYPENAME&) by, (const TYPENAME&) bz
  * @param (const TYPENAME&) cx, (const TYPENAME&) cy, (const TYPENAME&) cz
  */
 template< typename TYPENAME >
 GL_Triangle<TYPENAME>::GL_Triangle( const TYPENAME& ax, const TYPENAME& ay, const TYPENAME& az,
                                     const TYPENAME& bx, const TYPENAME& by, const TYPENAME& bz,
                                     const TYPENAME& cx, const TYPENAME& cy, const TYPENAME& cz )
                    : Triangle<TYPENAME> ( ax, ay, az,
                                           bx, by, bz,
                                           cx, cy, cz )
 {
    area = Triangle<TYPENAME>::getArea();
    center = Triangle<TYPENAME>::getCenter();
    normal = Triangle<TYPENAME>::getNormal();
 }
 
 /**
  * Alternative Constructor
  * The constructor takes in the three positions that defines the triangle.
  *
  * @param (const Vector3<TYPENAME>&) a
  * @param (const Vector3<TYPENAME>&) b
  * @param (const Vector3<TYPENAME>&) c
  */
 template< typename TYPENAME >
 GL_Triangle<TYPENAME>::GL_Triangle( const Vector3<TYPENAME>& a, const Vector3<TYPENAME>& b, const Vector3<TYPENAME>& c )
                                : Triangle<TYPENAME>( a, b, c )
 {
    area = Triangle<TYPENAME>::getArea();
    center = Triangle<TYPENAME>::getCenter();
    normal = Triangle<TYPENAME>::getNormal();
 }
 
 /**
  * Alternative Constructor
  * The constructor copies the content of the specified triangle.
  *
  * @param (const GL_Triangle<TYPENAME>&) t
  */
 template< typename TYPENAME >
 GL_Triangle<TYPENAME>::GL_Triangle( const GL_Triangle<TYPENAME>& t )
                                    : Triangle<TYPENAME>( t )
 {
    memcpy( this, &t, sizeof(GL_Triangle<TYPENAME>) );
 }
 
 /**
  * Alternative Constructor
  * The constructor copies the content of the specified triangle.
  *
  * @param (const Triangle<TYPENAME>&) t
  */
 template< typename TYPENAME >
 GL_Triangle<TYPENAME>::GL_Triangle( const Triangle<TYPENAME>& t ) 
                                : Triangle<TYPENAME>( t )
 {
    area = Triangle<TYPENAME>::getArea();
    center = Triangle<TYPENAME>::getCenter();
    normal = Triangle<TYPENAME>::getNormal();
 }
 
 /**
  * Destructor
  */
 template< typename TYPENAME >
 GL_Triangle<TYPENAME>::~GL_Triangle()
 {
 
 }
 
 /**
  * operator=() copies the content of the specified triangle to this triangle.
  *
  * @param (const GL_Triangle<TYPENAME>&) t
  */
 template< typename TYPENAME >
 inline GL_Triangle<TYPENAME>& GL_Triangle<TYPENAME>::operator=( const GL_Triangle<TYPENAME>& t )
 {
    memcpy( this, &t, sizeof(GL_Triangle<TYPENAME>) );
    return *this;
 }
 
 /**
  * operator=() copies the content of the specified triangle to this triangle.
  *
  * @param (const Triangle<TYPENAME>&) t
  */
 template< typename TYPENAME >
 inline GL_Triangle<TYPENAME>& GL_Triangle<TYPENAME>::operator=( const Triangle<TYPENAME>& t )
 {
    memcpy( this, &t, sizeof(Triangle<TYPENAME>) );
    area = Triangle<TYPENAME>::getArea();
    center = Triangle<TYPENAME>::getCenter();
    normal = Triangle<TYPENAME>::getNormal();
    return *this;
 }

 #ifdef OPTIMIZE_GL_TRIANGLE
 /**
  * getArea() returns the area of this triangle.
  *
  * @return const TYPENAME&
  */
 template <typename TYPENAME>
 inline const TYPENAME GL_Triangle<TYPENAME>::getArea() const
 {
    return area;
 }
 
 /**
  * getCenter() returns the center point of this triangle.
  *
  * @return const Vector3<TYPENAME>&
  */
 template <typename TYPENAME>
 inline const Vector3<TYPENAME> GL_Triangle<TYPENAME>::getCenter() const
 {
    return center;
 }
 
 /**
  * getNormal() returns the normal unit vector of this triangle.
  * Assumes counter-clockwise winding 
  * (i.e. if a to b is the vector (1, 0, 0) and a to c is the vector
  * (0, 1, 0), the resulting normal vector is (0, 0, 1)).
  *
  * @return const Vector3<TYPENAME>&
  */
 template <typename TYPENAME>
 inline const Vector3<TYPENAME> GL_Triangle<TYPENAME>::getNormal() const
 {
    return normal;
 }
 
 /** FRIEND FUNCTIONS **/
 
 /**
  * operator*() rescales the triangle.
  *
  * @param (Triangle<TYPENAME>&) t
  * @param (const TYPENAME&) scale
  */
 template <typename TYPENAME>
 inline GL_Triangle<TYPENAME> operator*( GL_Triangle<TYPENAME> t, const TYPENAME& scale )
 {
	t.a -= t.center;
	t.b -= t.center;
	t.c -= t.center;

	t.a *= scale;
	t.b *= scale;
	t.c *= scale;

	t.a += t.center;
	t.b += t.center;
	t.c += t.center;

	t.area = t.getArea();
    t.center = t.getCenter();
    t.normal = t.getNormal();

	return t;
 }

 /**
  * operator*() rescales the triangle.
  *
  * @param (const TYPENAME&) scale
  * @param (Triangle<TYPENAME>&) t
  */
 template <typename TYPENAME>
 inline GL_Triangle<TYPENAME> operator*( const TYPENAME& scale, GL_Triangle<TYPENAME> t )
 {
	return t*scale;
 }

 /**
  * area() returns the area of the specified triangle.
  *
  * @param (const GL_Triangle<TYPENAME>&) t
  * @return const TYPENAME&
  */
 template <typename TYPENAME>
 inline const TYPENAME& area( const GL_Triangle<TYPENAME>& t )
 {
    return t.area;
 }
 
 /**
  * center() returns the center point of the specified triangle.
  *
  * @param (const GL_Triangle<TYPENAME>&) t
  * @return const Vector3<TYPENAME>&
  */
 template <typename TYPENAME>
 inline const Vector3<TYPENAME>& center( const GL_Triangle<TYPENAME>& t )
 {
    return t.center;
 }
 
 /**
  * normal() returns the normal unit vector of the specified triangle.
  * Assumes counter-clockwise winding 
  * (i.e. if a to b is the vector (1, 0, 0) and a to c is the vector
  * (0, 1, 0), the resulting normal vector is (0, 0, 1)).
  *
  * @param (const GL_Triangle<TYPENAME>&) t
  * @return const Vector3<TYPENAME>&
  */
 template <typename TYPENAME>
 inline const Vector3<TYPENAME>& normal( const GL_Triangle<TYPENAME>& t )
 {
    return t.normal;
 }
 
 /**
  * center() returns the centroid of the specified group of triangles.
  *
  * @param (const std::vector< const GL_Triangle<TYPENAME> >&) triangles
  * @return Vector3<TYPENAME>
  */
 template <typename TYPENAME> 
 inline Vector3<TYPENAME> center( const std::vector< const GL_Triangle<TYPENAME> >& triangles )
 {
    // n = number of triangles
    // centroid = (1 / (3n)) * sum( ith_a + ith_b + ith_c )
    Vector3<TYPENAME> centroid( 0.0f, 0.0f, 0.0f );

    std::vector< const GL_Triangle<TYPENAME> >::const_iterator itr;
    for ( itr = triangles.begin(); itr != triangles.end(); ++itr )
	{
	    const GL_Triangle<TYPENAME>& triangle = *itr;
	    
	    // sum( ith_a + ith_b + ith_c )
	    centroid += center( triangle );
	}
	// centroid = (1 / (3n)) * sum( ith_a + ith_b + ith_c )
	return centroid /= triangles.size();
 }
 
 /**
  * center() returns the centroid of the specified group of triangles.
  *
  * @param (std::vector< const GL_Triangle<TYPENAME>* >&) triangles
  * @return Vector3<TYPENAME>
  */
 template <typename TYPENAME> 
 inline Vector3<TYPENAME> center( std::vector< const GL_Triangle<TYPENAME>* >& triangles )
 {
    // n = number of triangles
    // centroid = (1 / (3n)) * sum( ith_a + ith_b + ith_c )
    Vector3<TYPENAME> centroid( 0.0f, 0.0f, 0.0f );

    // WARNING: Changed here after removing const
    //std::vector<const GL_Triangle<TYPENAME> * const>::const_iterator itr;
    std::vector< const GL_Triangle<TYPENAME>* >::const_iterator itr;
    for ( itr = triangles.begin(); itr != triangles.end(); ++itr )
	{
	    const GL_Triangle<TYPENAME> * const & triangle_Ptr = *itr;
	    
	    // sum( ith_a + ith_b + ith_c )
	    centroid += center( triangle_Ptr );
	}
	// centroid = (1 / (3n)) * sum( ith_a + ith_b + ith_c )
	return centroid /= triangles.size();
 }
 #endif // OPTIMIZE_GL_TRIANGLE
 
 /**
  * apply() applies the specified orientation (matrix) to the specified triangle.
  *
  * @param (GL_Triangle<TYPENAME>&) t
  * @param (const OrientationMatrix3<TYPENAME>&) orientation
  * @return GL_Triangle<TYPENAME>&
  */
 template <typename TYPENAME>
 inline GL_Triangle<TYPENAME>& apply( GL_Triangle<TYPENAME>& t, const OrientationMatrix3<TYPENAME>& orientation )
 {
    t.a = orientation * t.a;
    t.b = orientation * t.b;
    t.c = orientation * t.c;
    
    t.center = Triangle<TYPENAME>::getCenter();//orientation * t.center;
    t.normal = orientation * t.normal;
    
    return t;
 }
 
 /**
  * apply() applies the specified translation (vector) to the specified triangle.
  *
  * @param (GL_Triangle<TYPENAME>&) t
  * @param (const Vector3<TYPENAME>&) translation
  * @return GL_Triangle<TYPENAME>&
  */
 template <typename TYPENAME>
 inline GL_Triangle<TYPENAME>& apply( GL_Triangle<TYPENAME>& t, const Vector3<TYPENAME>& translation )
 {
    t.a += translation;
    t.b += translation;
    t.c += translation;
    
    t.center += translation;

    return t;
 }
 
 /**
  * apply() applies the specified orientation (matrix) and translation (vector) to the specified triangle.
  *
  * @param (GL_Triangle<TYPENAME>&) t
  * @param (const OrientationMatrix3<TYPENAME>&) orientation
  * @param (const Vector3<TYPENAME>&) translation
  * @return GL_Triangle<TYPENAME>&
  */
 template <typename TYPENAME>
 inline GL_Triangle<TYPENAME>& apply( GL_Triangle<TYPENAME>& t, const OrientationMatrix3<TYPENAME>& orientation,                                                                               const Vector3<TYPENAME>& translation )
 {
    t.a = (orientation * t.a) + translation;
    t.b = (orientation * t.b) + translation;
    t.c = (orientation * t.c) + translation;
    
    t.center = (t.a + t.b + t.c) / 3.0f; //(orientation * t.center) + translation;
    t.normal = orientation * t.normal;
    
    return t;
 }
 
 /**
  * print() prints out the 3 positions that define the triangle 
  * and the area, center, and normal unit vector of the triangle.
  *
  * @param (const GL_Triangle<TYPENAME>&) t
  */
 template<typename TYPENAME>
 inline void print( const GL_Triangle<TYPENAME>& t )
 {
    printf( "GL_Triangle:\n" );
    printf( "a: " );
    print(t.a);
    printf( "b: " );
    print(t.b);
    printf( "c: " );
    print(t.c);
    printf( "area: %1f\n", t.area );
    printf( "center: " );
    print(t.center);
    printf( "normal: " );
    print(t.normal);
 }

 #endif // GL_TRIANGLE_H